home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Shareware Grab Bag
/
Shareware Grab Bag.iso
/
050
/
madtrb34.arc
/
CLONE.PAS
< prev
next >
Wrap
Pascal/Delphi Source File
|
1986-04-06
|
21KB
|
403 lines
(******************************************************************************
CLONE.PAS
Version 3
October 17, 1985
Borland SIG, CompuServe
by: Bob Tolz 70475,1071
with input from: Randy Forgaard 70307,521
Kim Kokkonen 72457,2131
Bela Lubkin 76703,3015
This Turbo Pascal program demonstrates how to have a program "clone" a copy of
itself as a new .COM file, after the user has entered any desired changes.
This is analogous to the Setup menus of Sidekick and SuperKey, and similar to
the effect that TINST would achieve if it were built right into the Turbo
compiler itself. It can also be used to clone slightly different versions of a
program, for different purposes.
What follows is a rather lengthy description of the implementation of this
program, called CLONE. However, CLONE.PAS is ready to run as-is. If you just
want to see what CLONE does, compile CLONE.PAS ==> to a .COM file <== (very
important), under DOS Turbo 2.0 or 3.0, and then run CLONE.COM. Furthermore,
don't be intimidated by the large size of this file; when you take out the
documentation and the test program, the resulting code only fills a screen or
two.
CLONE has been tested using all regular, 8087, and BCD versions of DOS Turbo
Pascal 2.00B, 3.00B, and 3.01A. (We will use DOS to mean both MS-DOS and
PC-DOS.) It probably runs without change with all other versions of the DOS
Turbo 2.0 and 3.0 compilers. If you have a different version of DOS Turbo, and
CLONE works for you, or you have modified CLONE to work for you, please let us
know, or upload a new copy of CLONE.
With some coaxing, CLONE could be modified to run under CP/M-86 and CP/M-80.
If you make CLONE work on any non-DOS system, please incorporate the new
procedures required into this file, and upload a new CLONE. We would love to
see CLONE become as generally applicable as possible.
The theory behind CLONE is as follows: CLONE is designed to be run as a
compiled Turbo program. When loaded into memory, it has the capability to make
a copy of itself, by just writing the image of itself, which already resides in
memory, to a new .COM file. If the user changes some typed constants in CLONE
before CLONE clones itself, the new copy of clone will reflect those new values
for the typed constants. Voila! A program that can "install" itself. The
trick in implementing CLONE is to find out where the memory image of CLONE.COM
begins, and how long it is, so that CLONE can write out the correct memory
image of itself as a new .COM file.
The first question is easy. When DOS loads CLONE.COM (or any .COM program)
into memory, it first builds a 256 byte ($100 bytes, in hexadecimal) Program
Segment Prefix (PSP) in memory, and then reads CLONE.COM into the memory
immediately following the PSP. DOS then sets the CS (code segment) register so
that address CS:0000 is the beginning of the PSP. Consequently, the contents
of CLONE.COM really start at CS:0100, or, in Turbo parlance, CSeg:$0100.
The second question, the code length of CLONE.COM in memory, is a little
trickier. One way to find out is to compile CLONE, issue a "DIR CLONE.COM"
command at the DOS prompt to see how big the .COM file is, and then hard-code
this constant into CLONE.COM. The disadvantage of this method is that every
time you change CLONE, you have to compile it, see how long it is, change that
constant in the CLONE code, and then re-compile CLONE.
We propose an alternate scheme, to have CLONE "discover," at run-time, how many
bytes the memory image of itself occupies. It turns out that .COM programs
produced by DOS Turbo set up the DS (data segment) register so that the address
DS:0000 is the first 16-byte paragraph boundary past the end of the .COM file
code. Depending on where the .COM code ends, there can be up to 15 "garbage"
bytes between the end of the .COM code and the beginning of the data segment,
because of the 16-byte paragraph boundary restriction on DS:0000. Thus, we
could say that the .COM code ends at DSeg:$0000 (in Turbo notation), and that
would be almost correct, but it would usually include a few extra garbage bytes
that weren't actually part of the .COM file.
One school of thought is to just keep those few garbage bytes as part of the
cloned .COM file that gets produced by CLONE. I.e., when CLONE writes out a
"copy" of itself, it could include all the bytes between CS:0100 and DS:0000,
including those few extra garbage bytes. Then, when you tell DOS to run the
cloned copy of the program, DOS will just load those garbage bytes into memory
along with the rest of the .COM file. These extra bytes will not affect the
execution of the cloned copy, any more than they affected the execution of
CLONE when they happened to be located in memory when CLONE decided to clone
itself. However, it may be disconcerting to have the cloned copy of a program
be a few bytes longer than the original, due to the garbage bytes. In
particular, you won't be able to use the DOS COMP program to compare the
original .COM file with its clone, because the files will be different sizes.
However, if you decide that the garbage bytes are all right, in the interest of
removing some complexity from CLONE, all you have to do is run CLONE.COM once
so that it makes a clone of itself (including the garbage bytes). If you use
the clone of CLONE to make another CLONE, the second CLONE will be exactly the
same size as the first clone of CLONE. Thus, if you use the clone of CLONE as
your distribution copy, all clones of the distribution copy will be the same
size as the distribution copy, so the DOS COMP program will be able to compare
clones. In summary, you can choose to use a cloned copy of CLONE, so that all
clones of the cloned CLONE will be the same size as the first clone of CLONE.
(There will be a quiz on this material.)
If you decide to use the simpler scheme that includes the few garbage bytes at
the end of the cloned .COM file, you can replace the body of the CodeSize
function, below, with the single statement:
CodeSize := ((DSeg - CSeg) shl 4) - $100
and omit the "while" loop and the variable declaration entirely.
However, suppose you want the clones to be the same size as the originals, and
you don't want to have to perform an extra cloning step prior to each time you
distribute your program. After some discussion with various DOS and Turbo
wizards, including Borland itself, we have not discovered any magic location in
the Turbo run-time system, or any DOS function call, or any field in the PSP,
that can be used to compute the actual length of the loaded .COM file, minus
the garbage bytes. However, we have found a reliable way to compute this
length by having CLONE examine the last few machine instructions of itself in
memory. In particular, we have found that .COM files that were compiled using
either the regular or the 8087 version of Turbo 2.00B always end in the
following pattern of bytes:
E9 00 00 E8 ? ?
where ? is any byte at all. This is probably true of any of the Turbo 2.0
compilers, not just the 2.00B compilers that we tested. Likewise, .COM files
produced by the regular, 8087, or BCD versions of Turbo 3.00B and 3.01A always
end in the following pattern of bytes:
? 00 00
where ? is any byte except E9. Again, this pattern probably holds for all
versions of Turbo 3.0, not just the 3.00B and 3.01A compilers we tested.
The CodeSize function, below, computes the length of the loaded .COM file at
run-time by examining the bytes immediately prior to DSeg:$0000 to find the
above patterns. For a pattern that is "n" bytes long, it looks for the first
occurrence of the pattern starting at the memory location that is 16-n+1 bytes
prior to DSeg:$0000. It turns out that the Turbo 2.0 pattern and Turbo 3.0
pattern are mutually exclusive, so there is no possibility of accidentally
finding the Turbo 2.0 pattern near the end of a Turbo 3.0 .COM file and vice
versa. Consequently, for simplicity of exposition, CodeSize looks for both the
Turbo 2.0 pattern and the Turbo 3.0 pattern at the same time, since there is no
danger of confusion. If you know for certain that you will only be using the
Turbo 3.0 compiler, you can remove the test for the Turbo 2.0 pattern from
CodeSize, and vice versa.
If you find byte patterns for any other versions of Turbo, or can verify that
the byte patterns used by the CodeSize function below already work for versions
of DOS Turbo other than 2.00B, 3.00B, and 3.01A (regular, 8087, and BCD),
please let us know, and/or upload a new copy of CLONE.PAS that incorporates the
appropriate generalization of the CodeSize function.
One way to discover the byte patterns at the end of a .COM file, produced by a
version of the Turbo compiler that we have not yet tried, is to compile several
programs, into .COM files, using that Turbo compiler, and use the DEBUG program
that comes with DOS to look for similarities at the ends of the .COM files.
For example, suppose DIR shows that TEST.COM, produced by your Turbo compiler,
has a length of 12396. Converting that to hex (which is $306C), and adding
$100 (for the PSP), indicates that memory location CS:316C will be the first
byte past the end of the .COM file. (It will be the first byte past the end,
rather than the last byte itself, because we start counting from CS:0000
instead of CS:0001.) CS:316B (one less than CS:316C) will be the actual last
byte of the .COM file. To find a a pattern, it is prudent to look at least at
the last $20 bytes (2 16-byte paragraphs) of the .COM file. For this example,
we issue the command:
D314C 316B
at the DEBUG "-" prompt. (The CS segment register is implicitly used by DEBUG
in this command.) DEBUG will display the bytes in those 32 locations. Save
the contents of the screen to your printer, or (if you have Sidekick) to a
file, and do a "Q" to quit out of DEBUG. Using DEBUG and your printer, compare
those bytes to the last 32 bytes of the .COM files produced by your Turbo
compiler for several other example programs. A pattern should emerge. Note
that the pattern will almost certainly consist of certain bytes that are always
the same, and certain "?" bytes that are less restricted. Once the pattern
has been found, it can be incorporated into CodeSize to extend the number of
Turbo compilers for which CodeSize will work.
If you run the DOS COMP program to compare CLONE.COM with a clone of itself,
you may find discrepancies at a few byte locations. For example, using the
regular 3.00B compiler, we found that the byte at $92 and the word (two bytes)
at $2BD2 differed between CLONE.COM and its clone. These reflect changes that
the Turbo run-time system makes to itself after CLONE.COM has been loaded by
DOS. Bela Lubkin of Borland has indicated that $92 is irrelevant, and that
$2BD2/$2BD3 is probably innocuous. If you do have a problem, or if you want to
make triple sure that there won't be a problem, then: 1) use COMP to see what
data should be in those byte locations, and 2) immediately before the
BlockWrite operation in the Clone procedure below, insert some assignment
statements to the appropriate Mem and MemW locations, e.g.:
Mem[CSeg:$0092] := ????
MemW[CSeg:$2CD2] := ????
The CodeSize function is only half of the story. The other half is the Clone
procedure itself. The Clone procedure uses the CodeSize function to determine
how long the new .COM file will be, and then writes those bytes directly from
memory to a new .COM file. Using Turbo 3.0 for DOS, this is very easy to do,
using BlockWrite and the new optional record size parameter for Rewrite,
allowing us to declare the record size to be one byte long. This allows us to
write a new .COM file that is exactly the right length.
Using Turbo 2.0, or a non-DOS version of Turbo 3.0, is more difficult. In
these cases, BlockWrite can only be used to write 128-byte blocks, to the
resulting .COM file will not be the exact byte length we are looking for. One
way around this is to use a File of Byte, and to write the new .COM file one
byte at a time, but this turns out to be dreadfully slow. Instead, we have
opted below for a DOS-specific version of Clone that uses DOS function calls
(assumes DOS 2.0 or higher) to write the new .COM file very quickly.
We have written two versions of the Clone procedure below. The first one,
involving DOS function calls, will work under both Turbo 2.0 and 3.0. The
second version of Clone, which is much cleaner than the first and runs just as
fast, can only be compiled under DOS Turbo 3.0. As this CLONE.PAS file
currently exists, the first Clone procedure will be used, due to a sneaky
commenting convention. However, as indicated in the comment immediately
preceding the first Clone procedure, deleting a single character from this
source file will cause the second, more compact Clone procedure to be used
instead.
A caution: The CLONE technique has not been tried with any programs that use
overlays. This could be quite tricky, because the memory image of CLONE would
change as soon as program execution caused an overlay to be read from disk.
The CLONE idea might be usable with overlayed programs if no overlays get
loaded prior to cloning, but this has not been tested. Please let us know if
you come up with any results or insights in this regard.
CP/M-86 users: We would be very interested if someone could add, to this
CLONE.PAS file, a special version of the Clone procedure, and an enhancement or
new version of the CodeSize function, that will work for CP/M-86. A CodeSize
function will probably still be required if one wishes to avoid cloning the
garbage bytes between the end of the .CMD file image and the beginning of the
data segment. The byte patterns to look for will probably be different for
CP/M-86 than for DOS, so additional clauses will probably have to be added to
the "while" loop in the CodeSize function. The subtraction of $100, in
CodeSize, should be omitted, since CP/M-86 sets the CS segment register such
that CS:0000 (rather than CS:0100) is the beginning of the loaded program.
Also, the Clone procedure will have to be rewritten, either to use a File of
Byte (slow), or to replace the DOS function calls with CP/M-86 function calls.
CP/M-80 users: It would be terrific if someone could CP/M-80 capability to this
CLONE.PAS file. It appears, from reading the Turbo manual, that the heap
(rather than the data segment) starts immediately after the object code of the
loaded .COM file, with no garbage bytes in-between. Thus, the length of the
.COM file can probably be determined by subtracting, from the initial value of
HeapPtr, the size of CP/M and its run-time workspace. It is pretty clear, in
any case, that the CodeSize function for CP/M-80 would be very different, and
probably much simpler. The Clone procedure will have to be rewritten also, so
as not to use DOS function calls or features that are specific to the DOS
version of Turbo.
We hereby donate CLONE to the Public Domain...happy cloning!
******************************************************************************)
program CloneDemo;
type
FileName = string[80];
Message = string[80];
{This function returns the number of bytes occupied by the image of this .COM
file in memory. Known to work for Turbo programs compiled under the regular,
8087, and BCD versions of the Turbo 2.00B, 3.00B, and 3.01A compilers for DOS.
See comments above for details.}
function CodeSize: Integer;
var
i: Byte;
begin
i := 11;
while {Turbo version is marked on the left:}
{3.0:} not ((Mem [DSeg-2:i+3] <> $00E9) and (MemW[DSeg-2:i+4] = $0000)) and
{2.0:} not ((MemW[DSeg-2:i+0] = $00E9) and (MemW[DSeg-2:i+2] = $E800)) do
i := i + 1;
CodeSize := ((((DSeg - 2) - CSeg) shl 4) + i + 6) - $100
end {CodeSize};
(*Currently, the first Clone procedure below will be compiled. To use the
second Clone procedure, delete the curly bracket on the line immediately
following this comment.*)
{
(*The next line is part of the Clone selection scheme.*)
(* }
{Writes, into the file "fn," a clone of this program, including any new values
for typed constants that may have been changed since the program was loaded.
This is the long version of Clone that uses DOS function calls (assumes DOS
2.0 or higher) to quickly write out the new .COM file. It can be used both
with the 2.0 and the 3.0 versions of Turbo for DOS. See comments above for
details.}
procedure Clone (fn: FileName);
procedure Abort(msg: Message);
begin
writeln(msg);
Halt
end {Abort};
var
handle, length: Integer;
regPack: record
case Integer of
1: (AX, BX, CX, DX, BP, SI, DI, DS, ES, Flags: Integer);
2: (AL, AH, BL, BH, CL, CH, DL, DH: Byte)
end;
writeError: Boolean;
begin
with regPack do
begin
fn := fn + #0; {Convert "fn" to an ASCIIZ string}
length := CodeSize; {Length of code image in memory}
AH := $3C; {Create a file}
DS := Seg(fn[1]); {Segment of ASCIIZ file name}
DX := Ofs(fn[1]); {Offset of ASCIIZ file name}
CX := 0; {Default attributes}
MsDos(regPack); {Create the clone file}
if Odd(Flags) then {Check if carry bit is set}
Abort('Unable to create file');
handle := regPack.AX; {Retrieve handle for opened file}
AH := $40; {Write to a file}
BX := handle; {File to write to}
DS := CSeg; {Segment of code}
DX := $100; {Beginning address of code}
CX := length; {Length of code}
MsDos(regPack); {Write the code to the clone file}
writeError := Odd(Flags) or (AX <> length);
if writeError then {Allow the file to be closed, anyway}
writeln('Unable to write to file');
AH := $3E; {Close a file}
BX := handle; {File to close}
MsDos(regPack); {Close the output file}
if Odd(Flags) then {Check if carry bit is set}
Abort('Unable to close file');
if writeError then Halt {Halt if there was a write error previously}
end
end {Clone};
{The next line is part of the Clone selection scheme.}
{ *)
(*Writes, into the file "fn," a clone of this program, including any new values
for typed constants that may have been changed since the program was loaded.
This is the more elegant version of CLONE that uses BlockWrite and the
optional record size parameter to Rewrite to quickly write out the new .COM
file. It can only be used with DOS Turbo 3.0 and higher. See comments above
for details.*)
procedure Clone (fn: FileName);
var
f: File;
contents: Byte Absolute CSeg:$0100;
begin
Assign(f, fn);
Rewrite(f, 1);
BlockWrite(f, contents, CodeSize);
Close(f)
end (*Clone*);
(*The next line is part of the Clone selection scheme.*)
{ }
{Test program for CLONE:}
const
default: Message =
'"How many Fortune 1000 companies are there, anyway?" -- Steve Jobs';
var
newFile: FileName;
new: Message;
begin
writeln('The size of this .COM file is ', CodeSize, ' bytes.');
writeln('The program currently contains the following default string:');
writeln;
writeln('=> ', default);
writeln;
writeln('Please enter a new value for this default string, or just press ');
writeln('Enter (Return) if you do not want to change the default value: ');
writeln;
write('=> ');
readln(new);
writeln;
if Length(new) > 0 then default := new;
writeln('Please enter a file name (ending in .COM) for storing the clone, ');
writeln('which can be the same name as the original .COM file, or just ');
writeln('press Enter (Return) if you do not want to make a clone: ');
write('=> ');
readln(newFile);
if Length(newFile) > 0 then
begin
Clone(newFile);
writeln;
writeln('If you now run "', newFile,
'," you will see the new default string.');
writeln('This program has just changed its own defaults!')
end
end {CloneDemo}.